// 判断是否指向了同一个对象
function defaultEqualityCheck(a, b) {
  return a === b;
}

/***
 * 比较前后的参数是否相等
 * @param equalityCheck 比较函数，默认采用上面说到的全等比较函数
 * @param prev 上一份参数
 * @param next 当前份参数
 * @returns {boolean}
 */
function areArgumentsShallowlyEqual(equalityCheck, prev, next) {
  if (prev === null || next === null || prev.length !== next.length) {
    return false;
  }

  // Do this in a for loop (and not a `forEach` or an `every`) so we can determine equality as fast as possible.
  // 因为for循环中途可以break.
  var length = prev.length;
  for (var i = 0; i < length; i++) {
    // 只要遇到一个不相等就返回
    if (!equalityCheck(prev[i], next[i])) {
      return false;
    }
  }

  return true;
}

/**
 * 默认的记忆函数
 * defaultMemoize记住在func参数中传递的函数。这是所使用的备忘录功能createSelector。
 * defaultMemoize 缓存大小为1。这意味着它总是在参数值更改时重新计算。
 * defaultMemoize通过调用equalityCheck函数确定参数是否已更改。正如defaultMemoize用于不可变数据的设计那样，默认equalityCheck函数使用引用相等性检查更改：
 * @param func 根据依赖的值，计算出新的值的函数
 * @returns {Function}
 */
// defaultMemoize(func, equalityCheck = defaultEqualityCheck)
function defaultMemoize(func) {
  var equalityCheck = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : defaultEqualityCheck;
  // 存储上一次计算得到的结果和依赖的参数
  var lastArgs = null;
  var lastResult = null;
  // 这里为了性能原因。使用arguments而不是rest运算符
  // we reference arguments instead of spreading them for performance reasons
  return function () {
    if (!areArgumentsShallowlyEqual(equalityCheck, lastArgs, arguments)) {
      // 参数改变了重新计算一遍
      // apply arguments instead of spreading for performance.
      lastResult = func.apply(null, arguments);
    }
    // 记录下这次的参数值
    lastArgs = arguments;
    return lastResult;
  };
}

/**
 * 用来保证inputSelectors的每一项都是函数，非函数就报错。然后返回每一个inputSelector组成的数组（依赖）
 * @param funcs
 * @returns {*}
 */
function getDependencies(funcs) {
  var dependencies = Array.isArray(funcs[0]) ? funcs[0] : funcs;

  if (!dependencies.every(function (dep) {
      return typeof dep === 'function';
    })) {
    var dependencyTypes = dependencies.map(function (dep) {
      return typeof dep;
    }).join(', ');
    throw new Error('Selector creators expect all input-selectors to be functions, ' + ('instead received the following types: [' + dependencyTypes + ']'));
  }

  return dependencies;
}

/**
 * 创建选择器创建器
 * createSelectorCreator可用于制作的自定义版本createSelector。
 * 该memoize参数是要替换的记忆功能defaultMemoize。
 * 在...memoizeOptions其余的参数传递零个或多个配置选项memoizeFunc。选择器resultFunc作为第一个参数传递给memoize和，然后memoizeOptions作为第二个参数传递：
 * @param memoize 记忆函数
 * @returns {Function}
 */
// createSelectorCreator（memoize，... memoizeOptions）
function createSelectorCreator(memoize) {
  for (var _len = arguments.length, memoizeOptions = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
    memoizeOptions[_key - 1] = arguments[_key];
  }

  return function () {
    for (var _len2 = arguments.length, funcs = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
      funcs[_key2] = arguments[_key2];
    }

    var recomputations = 0;
    var resultFunc = funcs.pop();
    var dependencies = getDependencies(funcs);

    // 缓存resultFunc函数
    var memoizedResultFunc = memoize.apply(undefined, [function () {
      recomputations++;
      // apply arguments instead of spreading for performance.
      return resultFunc.apply(null, arguments);
    }].concat(memoizeOptions));

    // 如果使用完全相同的参数调用选择器，则无需再次遍历我们的依赖项。(如果state指向同一块内存那么就不去重新取值)
    // If a selector is called with the exact same arguments we don't need to traverse our dependencies again.
    var selector = memoize(function () {
      var params = [];
      var length = dependencies.length;

      for (var i = 0; i < length; i++) {
        // 直接将参数结果记录
        // apply arguments instead of spreading and mutate a local list of params for performance.
        params.push(dependencies[i].apply(null, arguments));
      }

      // 缓存resultFunc
      // apply arguments instead of spreading for performance.
      return memoizedResultFunc.apply(null, params);
    });

    selector.resultFunc = resultFunc;
    selector.dependencies = dependencies;
    // 计算计算次数
    selector.recomputations = function () {
      return recomputations;
    };
    // 重置计算计算次数
    selector.resetRecomputations = function () {
      return recomputations = 0;
    };
    return selector;
  };
}

// createSelector(...inputSelectors | [inputSelectors], resultFunc)
var createSelector = createSelectorCreator(defaultMemoize);

var a = {a: 1, b: 2};

const totalSelector = createSelector(
  a => a.a,
  a => a.b,
  (subtotal, tax) => ({total: subtotal + tax})
)

console.log(totalSelector(a));
a.a = 2;
// a = {...a};
console.log(totalSelector(a));
